package org.phalanx.map;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

/*
 * This data structure deals with the Map that players will interact
 * with in the game. The server will also use this map object to define
 * the game and do checks on valid moves. On the client side this will be
 * plugged into a Graphical engine that renders the map.
 * 
 * On the client side this should not be modified on its own but
 * rather modified by a overlaying class.
 * 
 * */
public class PhalanxMap {
	private Hashtable<MapCoordinates, MapTile> pMapTable;
	private Integer mapLength;
	private Integer mapWidth;
	private Integer tileSize; // Size must be square (i.e 48x48)
	public static final Integer DEFAULT_TILE_SIZE = 48;
	private String mapName;
	private String mapVersion;
	private String getAuthroName;
	private String getAuthorEmail;
	private String getAuthorCountry;
	private String getAuthorURL;
	private MapTileSet mapTileSet;
	private String mapFilePath;
	private MapTile defaultTile;
	private ArrayList<MapCoordinates> buildPointList = new ArrayList<MapCoordinates>();
	
	public PhalanxMap(){
		pMapTable = new Hashtable<MapCoordinates, MapTile>();
		tileSize = DEFAULT_TILE_SIZE;
	}
	
	public void setMapDimensions(Integer l, Integer w){
		mapLength = l;
		mapWidth = w;
		
		if (l > 0 && w > 0) {
			clearUnusedTiles();
			applyDefaultTilesToBlankSquares();
		}
	}
	
	private void clearUnusedTiles() {
		Enumeration<MapCoordinates> e = this.pMapTable.keys();
		
		while (e.hasMoreElements()) {
			MapCoordinates c = e.nextElement();
			
			if (c.getX() > mapWidth - 1) {
				pMapTable.remove(c);
				if (this.buildPointList.contains(c)) {
					this.buildPointList.remove(c);
				}
			}
			if (c.getY() > mapLength - 1) {
				pMapTable.remove(c);
				if (this.buildPointList.contains(c)) {
					this.buildPointList.remove(c);
				}
			}
		}
	}

	private void applyDefaultTilesToBlankSquares() {
		if (this.getDefaultTile() != null) {
			for (int i = 0; i < mapWidth; i++) {
				for (int j = 0; j < mapLength; j++) {
					MapCoordinates c = new MapCoordinates(i,j);
					if (this.pMapTable.get(c) == null) {
						this.setMapTile(c, this.getDefaultTile());
					}
				}
			}
		}
	}
	
	public Integer getMapLength(){
		return mapLength;
	}
	
	public Integer getMapWidth(){
		return mapWidth;
	}
	
	public void setMapTile(MapCoordinates c, MapTile pMapTile){
		if (isBounds(c)){
			if (pMapTile.getTerrain() != null && pMapTile.getImageReference() != null){
				pMapTable.put(c, pMapTile);
			}
		}
	}
	
	public void setMapTile(Integer xValue, Integer yValue, MapTile pMapTile){
		if (xValue != null && yValue != null){
			setMapTile(new MapCoordinates(xValue, yValue), pMapTile);
		}
	}
	
	public void removeMapTile(MapCoordinates c){
		if (isBounds(c)){
			pMapTable.remove(c);
		}
	}
	
	public void removeMapTile(Integer xValue, Integer yValue){
		if (xValue != null && yValue != null){
			removeMapTile(new MapCoordinates(xValue, yValue));
		}
	}
	
	public MapTile getMapTileAt(MapCoordinates c){
		MapTile thisMapTile = null;
		
		if (isBounds(c)){
			thisMapTile = pMapTable.get(c);
		}
		
		return thisMapTile;
	}
	
	public MapTile getMapTileAt(Integer xValue, Integer yValue){
		return (xValue != null && yValue != null) ? getMapTileAt(new MapCoordinates(xValue, yValue)) : null;
	}
	
	public void setTileSize(Integer size){
		if (size != null && size > 0){
			this.tileSize = size;
		}
	}

	public void setDefaultMapTile(MapTile defaultTile, boolean modifyMap){
		if (defaultTile != null){
			this.defaultTile = defaultTile;
			if (modifyMap){
				for (int i = 0; i < this.getMapLength(); i++){
					for (int j = 0; j < this.getMapWidth(); j++){
						MapCoordinates newCoor = new MapCoordinates(j,i);
						this.setMapTile(newCoor, defaultTile);
					}
				}
			}
		}
	}
	
	public MapTile getDefaultTile(){
		return defaultTile;
	}
	
	public void clearMap(){
		this.pMapTable.clear();
	}
	
	public Integer getTileSize(){
		return this.tileSize;
	}
	
	private boolean isBounds(MapCoordinates c){
		boolean isInBounds = false;
		
		if (c != null && c.getX() != null && c.getY() != null){
			if (c.getX() >= 0 && c.getY() >= 0){
				if (c.getX() <= (mapLength -1) || c.getY() <= (mapWidth - 1)){
					isInBounds = true;
				}
			}
		}
		
		return isInBounds;
	}

	public String getMapName() {
		return mapName;
	}

	public void setMapName(String mapName) {
		this.mapName = mapName;
	}

	public String getMapVersion() {
		return mapVersion;
	}

	public void setMapVersion(String mapVersion) {
		this.mapVersion = mapVersion;
	}

	public MapTileSet getMapTileSet() {
		return mapTileSet;
	}

	public void setMapTileSet(MapTileSet mapTileSet) {
		this.mapTileSet = mapTileSet;
	}

	public String getMapFilePath() {
		return mapFilePath;
	}

	public void setMapFilePath(String mapFilePath) {
		this.mapFilePath = mapFilePath;
	}
	
	public void addBuildPoint(MapCoordinates c){
		if (!buildPointList.contains(c)){
			if (c.getX() < this.getMapWidth() && c.getY() < this.getMapLength()){
				if (!hasAdjacentBuildPoints(c)){
					buildPointList.add(c);
				}
			}
		}
	}
	
	private boolean hasAdjacentBuildPoints(MapCoordinates c){
		boolean isAdjacent = false;
		
		Integer startX = c.getX() - 1 < 0 ? c.getX() : c.getX() - 1;
		Integer startY = c.getY() - 1 < 0 ? c.getY() : c.getY() - 1;
		Integer endX = c.getX() + 1 > this.getMapWidth() - 1 ? c.getX() : c.getX() + 1;
		Integer endY = c.getY() + 1 > this.getMapLength() - 1 ? c.getY() : c.getY() + 1;
		
		for (int i = startX; i <= endX; i++){
			for (int j = startY; j <= endY; j++){
				MapCoordinates newCoord = new MapCoordinates(i,j);
				if (!newCoord.equals(c)){
					if (buildPointList.contains(newCoord)){
						isAdjacent = true;
					}
				}
			}
		}
		return isAdjacent;
	}
	
	public void removeBuildPoint(MapCoordinates c){
		if (buildPointList.contains(c)){
			buildPointList.remove(c);
		}
	}
	
	public boolean getIsBuildPoint(MapCoordinates c){
		return buildPointList.contains(c);
	}
	
	public boolean getIsBuildPointsEmpty(){
		return buildPointList.isEmpty();
	}
	
	public ArrayList<MapCoordinates> getBuildPoints(){
		return buildPointList;
	}

	public String getAuthorName() {
		return getAuthroName;
	}

	public void setAuthorName(String getAuthroName) {
		this.getAuthroName = getAuthroName;
	}

	public String getAuthorEmail() {
		return getAuthorEmail;
	}

	public void setAuthorEmail(String getAuthorEmail) {
		this.getAuthorEmail = getAuthorEmail;
	}

	public String getAuthorCountry() {
		return getAuthorCountry;
	}

	public void setAuthorCountry(String getAuthorCountry) {
		this.getAuthorCountry = getAuthorCountry;
	}

	public String getAuthorURL() {
		return getAuthorURL;
	}

	public void setAuthorURL(String getAuthorURL) {
		this.getAuthorURL = getAuthorURL;
	}
}
